home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / pine / imap-3.0 / ANSI / c-client / imap2.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-06-19  |  63.2 KB  |  2,003 lines

  1. /*
  2.  * Program:    Interactive Mail Access Protocol 2 (IMAP2) routines
  3.  *
  4.  * Author:    Mark Crispin
  5.  *        Networks and Distributed Computing
  6.  *        Computing & Communications
  7.  *        University of Washington
  8.  *        Administration Building, AG-44
  9.  *        Seattle, WA  98195
  10.  *        Internet: MRC@CAC.Washington.EDU
  11.  *
  12.  * Date:    15 June 1988
  13.  * Last Edited:    19 June 1993
  14.  *
  15.  * Sponsorship:    The original version of this work was developed in the
  16.  *        Symbolic Systems Resources Group of the Knowledge Systems
  17.  *        Laboratory at Stanford University in 1987-88, and was funded
  18.  *        by the Biomedical Research Technology Program of the National
  19.  *        Institutes of Health under grant number RR-00785.
  20.  *
  21.  * Original version Copyright 1988 by The Leland Stanford Junior University
  22.  * Copyright 1993 by the University of Washington
  23.  *
  24.  *  Permission to use, copy, modify, and distribute this software and its
  25.  * documentation for any purpose and without fee is hereby granted, provided
  26.  * that the above copyright notices appear in all copies and that both the
  27.  * above copyright notices and this permission notice appear in supporting
  28.  * documentation, and that the name of the University of Washington or The
  29.  * Leland Stanford Junior University not be used in advertising or publicity
  30.  * pertaining to distribution of the software without specific, written prior
  31.  * permission.  This software is made available "as is", and
  32.  * THE UNIVERSITY OF WASHINGTON AND THE LELAND STANFORD JUNIOR UNIVERSITY
  33.  * DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD TO THIS SOFTWARE,
  34.  * INCLUDING WITHOUT LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
  35.  * FITNESS FOR A PARTICULAR PURPOSE, AND IN NO EVENT SHALL THE UNIVERSITY OF
  36.  * WASHINGTON OR THE LELAND STANFORD JUNIOR UNIVERSITY BE LIABLE FOR ANY
  37.  * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
  38.  * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
  39.  * CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF
  40.  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  41.  *
  42.  */
  43.  
  44.  
  45. #include <ctype.h>
  46. #include <stdio.h>
  47. #include "mail.h"
  48. #include "osdep.h"
  49. #include "imap2.h"
  50. #include "misc.h"
  51.  
  52. /* Driver dispatch used by MAIL */
  53.  
  54. DRIVER imapdriver = {
  55.   "imap2",            /* driver name */
  56.   (DRIVER *) NIL,        /* next driver */
  57.   map_valid,            /* mailbox is valid for us */
  58.   map_parameters,        /* manipulate parameters */
  59.   map_find,            /* find mailboxes */
  60.   map_find_bboards,        /* find bboards */
  61.   map_find_all,            /* find all mailboxes */
  62.   map_find_all_bboards,        /* find all bboards */
  63.   map_subscribe,        /* subscribe to mailbox */
  64.   map_unsubscribe,        /* unsubscribe from mailbox */
  65.   map_subscribe_bboard,        /* subscribe to bboard */
  66.   map_unsubscribe_bboard,    /* unsubscribe from bboard */
  67.   map_create,            /* create mailbox */
  68.   map_delete,            /* delete mailbox */
  69.   map_rename,            /* rename mailbox */
  70.   map_open,            /* open mailbox */
  71.   map_close,            /* close mailbox */
  72.   map_fetchfast,        /* fetch message "fast" attributes */
  73.   map_fetchflags,        /* fetch message flags */
  74.   map_fetchstructure,        /* fetch message envelopes */
  75.   map_fetchheader,        /* fetch message header only */
  76.   map_fetchtext,        /* fetch message body only */
  77.   map_fetchbody,        /* fetch message body section */
  78.   map_setflag,            /* set message flag */
  79.   map_clearflag,        /* clear message flag */
  80.   map_search,            /* search for message based on criteria */
  81.   map_ping,            /* ping mailbox to see if still alive */
  82.   map_check,            /* check for new messages */
  83.   map_expunge,            /* expunge deleted messages */
  84.   map_copy,            /* copy messages to another mailbox */
  85.   map_move,            /* move messages to another mailbox */
  86.   map_append,            /* append string message to mailbox */
  87.   map_gc            /* garbage collect stream */
  88. };
  89.  
  90.                 /* prototype stream */
  91. MAILSTREAM imapproto = {&imapdriver};
  92.  
  93.                 /* driver parameters */
  94. long map_maxlogintrials = MAXLOGINTRIALS;
  95. long map_lookahead = MAPLOOKAHEAD;
  96. long map_port = IMAPTCPPORT;
  97.  
  98. /* Mail Access Protocol validate mailbox
  99.  * Accepts: mailbox name
  100.  * Returns: our driver if name is valid, NIL otherwise
  101.  */
  102.  
  103. DRIVER *map_valid (char *name)
  104. {
  105.   return mail_valid_net (name,&imapdriver,NIL,NIL);
  106. }
  107.  
  108.  
  109. /* Mail Access Protocol manipulate driver parameters
  110.  * Accepts: function code
  111.  *        function-dependent value
  112.  * Returns: function-dependent return value
  113.  */
  114.  
  115. void *map_parameters (long function,void *value)
  116. {
  117.   switch ((int) function) {
  118.   case SET_MAXLOGINTRIALS:
  119.     map_maxlogintrials = (long) value;
  120.   case GET_MAXLOGINTRIALS:
  121.     return (void *) map_maxlogintrials;
  122.   case SET_LOOKAHEAD:
  123.     map_lookahead = (long) value;
  124.   case GET_LOOKAHEAD:
  125.     return (void *) map_lookahead;
  126.   case SET_PORT:
  127.     map_port = (long) value;
  128.   case GET_PORT:
  129.     return (void *) map_port;
  130.   default:
  131.     break;
  132.   }
  133.   fatal ("Invalid map_parameters function");
  134. }
  135.  
  136. /* Mail Access Protocol find list of mailboxes
  137.  * Accepts: mail stream
  138.  *        pattern to search
  139.  */
  140.  
  141. void map_find (MAILSTREAM *stream,char *pat)
  142. {
  143.   void *s = NIL;
  144.   char *t,*bbd,*patx,tmp[MAILTMPLEN];
  145.   if (stream) {            /* have a mailbox stream open? */
  146.                 /* begin with a host specification? */
  147.     if (((*pat == '{') || ((*pat == '*') && (pat[1] == '{'))) &&
  148.     (t = strchr (pat,'}')) && *(patx = ++t)) {
  149.       if (*pat == '*') pat++;    /* yes, skip leading * (old Pine behavior) */
  150.       strcpy (tmp,pat);        /* copy host name */
  151.       tmp[patx-pat] = '\0';    /* tie off prefix */
  152.       LOCAL->prefix = cpystr (tmp);
  153.     }
  154.     else patx = pat;        /* use entire specification */
  155.     if (LOCAL && LOCAL->use_find &&
  156.     !strcmp (imap_send (stream,"FIND MAILBOXES",patx,NIL)->key,"BAD"))
  157.       LOCAL->use_find = NIL;    /* note no finding with this server */
  158.     if (LOCAL->prefix) fs_give ((void **) &LOCAL->prefix);
  159.   }
  160.   else while (t = sm_read (&s))    /* read subscription database */
  161.     if ((*t != '*') && mail_valid_net (t,&imapdriver,NIL,NIL) && pmatch(t,pat))
  162.       mm_mailbox (t);
  163. }
  164.  
  165. /* Mail Access Protocol find list of bboards
  166.  * Accepts: mail stream
  167.  *        pattern to search
  168.  */
  169.  
  170. void map_find_bboards (MAILSTREAM *stream,char *pat)
  171. {
  172.   void *s = NIL;
  173.   char *t,*bbd,*patx,tmp[MAILTMPLEN];
  174.   if (stream) {            /* have a mailbox stream open? */
  175.                 /* begin with a host specification? */
  176.     if (((*pat == '{') || ((*pat == '*') && (pat[1] == '{'))) &&
  177.     (t = strchr (pat,'}')) && *(patx = ++t)) {
  178.       if (*pat == '*') pat++;    /* yes, skip leading * (old Pine behavior) */
  179.       strcpy (tmp,pat);        /* copy host name */
  180.       tmp[patx-pat] = '\0';    /* tie off prefix */
  181.       LOCAL->prefix = cpystr (tmp);
  182.     }
  183.     else patx = pat;        /* use entire specification */
  184.                 /* this is optional, so no complaint if fail */
  185.     if (stream && LOCAL && LOCAL->use_find && LOCAL->use_bboard &&
  186.     !strcmp (imap_send (stream,"FIND BBOARDS",patx,NIL)->key,"BAD"))
  187.       LOCAL->use_bboard = NIL;
  188.     if (LOCAL->prefix) fs_give ((void **) &LOCAL->prefix);
  189.   }
  190.   else while (t = sm_read (&s))    /* read subscription database */
  191.     if ((*t == '*') && mail_valid_net (++t,&imapdriver,NIL,NIL) &&
  192.     pmatch (t,pat)) mm_bboard (t);
  193. }
  194.  
  195. /* Mail Access Protocol find list of all mailboxes
  196.  * Accepts: mail stream
  197.  *        pattern to search
  198.  */
  199.  
  200. void map_find_all (MAILSTREAM *stream,char *pat)
  201. {
  202.   char *t,*bbd,*patx,tmp[MAILTMPLEN];
  203.   if (stream) {            /* have a mailbox stream open? */
  204.                 /* begin with a host specification? */
  205.     if (((*pat == '{') || ((*pat == '*') && (pat[1] == '{'))) &&
  206.     (t = strchr (pat,'}')) && *(patx = ++t)) {
  207.       if (*pat == '*') pat++;    /* yes, skip leading * (old Pine behavior) */
  208.       strcpy (tmp,pat);        /* copy host name */
  209.       tmp[patx-pat] = '\0';    /* tie off prefix */
  210.       LOCAL->prefix = cpystr (tmp);
  211.     }
  212.     else patx = pat;        /* use entire specification */
  213.                 /* this is optional, so no complaint if fail */
  214.     if (LOCAL && LOCAL->use_find &&
  215.     !strcmp (imap_send (stream,"FIND ALL.MAILBOXES",patx,NIL)->key,"BAD")){
  216.       map_find (stream,pat);    /* perhaps older server */
  217.                 /* always include INBOX for consistency */
  218.       if (pmatch (pat,"INBOX")) mm_mailbox ("INBOX");
  219.     }
  220.     if (LOCAL->prefix) fs_give ((void **) &LOCAL->prefix);
  221.   }
  222. }
  223.  
  224.  
  225. /* Mail Access Protocol find list of all bboards
  226.  * Accepts: mail stream
  227.  *        pattern to search
  228.  */
  229.  
  230. void map_find_all_bboards (MAILSTREAM *stream,char *pat)
  231. {
  232.   char *t,*bbd,*patx,tmp[MAILTMPLEN];
  233.   if (stream) {            /* have a mailbox stream open? */
  234.                 /* begin with a host specification? */
  235.     if (((*pat == '{') || ((*pat == '*') && (pat[1] == '{'))) &&
  236.     (t = strchr (pat,'}')) && *(patx = ++t)) {
  237.       if (*pat == '*') pat++;    /* yes, skip leading * (old Pine behavior) */
  238.       strcpy (tmp,pat);        /* copy host name */
  239.       tmp[patx-pat] = '\0';    /* tie off prefix */
  240.       LOCAL->prefix = cpystr (tmp);
  241.     }
  242.     else patx = pat;        /* use entire specification */
  243.                 /* this is optional, so no complaint if fail */
  244.     if (LOCAL && LOCAL->use_find &&
  245.     !strcmp (imap_send (stream,"FIND ALL.BBOARDS",patx,NIL)->key,"BAD"))
  246.       map_find_bboards (stream,pat);
  247.     if (LOCAL->prefix) fs_give ((void **) &LOCAL->prefix);
  248.   }
  249. }
  250.  
  251. /* Mail Access Protocol subscribe to mailbox
  252.  * Accepts: mail stream
  253.  *        mailbox to add to subscription list
  254.  * Returns: T on success, NIL on failure
  255.  */
  256.  
  257. long map_subscribe (MAILSTREAM *stream,char *mailbox)
  258. {
  259.   return map_manage (stream,mailbox,"Subscribe Mailbox",NIL);
  260. }
  261.  
  262.  
  263. /* Mail access protocol unsubscribe to mailbox
  264.  * Accepts: mail stream
  265.  *        mailbox to delete from manage list
  266.  * Returns: T on success, NIL on failure
  267.  */
  268.  
  269. long map_unsubscribe (MAILSTREAM *stream,char *mailbox)
  270. {
  271.   return map_manage (stream,mailbox,"Unsubscribe Mailbox",NIL);
  272. }
  273.  
  274.  
  275. /* Mail Access Protocol subscribe to bboard
  276.  * Accepts: mail stream
  277.  *        mailbox to add to manage list
  278.  * Returns: T on success, NIL on failure
  279.  */
  280.  
  281. long map_subscribe_bboard (MAILSTREAM *stream,char *mailbox)
  282. {
  283.   return map_manage (stream,mailbox,"Subscribe BBoard",NIL);
  284. }
  285.  
  286.  
  287. /* Mail access protocol unsubscribe to bboard
  288.  * Accepts: mail stream
  289.  *        mailbox to delete from manage list
  290.  * Returns: T on success, NIL on failure
  291.  */
  292.  
  293. long map_unsubscribe_bboard (MAILSTREAM *stream,char *mailbox)
  294. {
  295.   return map_manage (stream,mailbox,"Unsubscribe BBoard",NIL);
  296. }
  297.  
  298. /* Mail Access Protocol create mailbox
  299.  * Accepts: mail stream
  300.  *        mailbox name to create
  301.  * Returns: T on success, NIL on failure
  302.  */
  303.  
  304. long map_create (MAILSTREAM *stream,char *mailbox)
  305. {
  306.   return map_manage (stream,mailbox,"Create",NIL);
  307. }
  308.  
  309.  
  310. /* Mail Access Protocol delete mailbox
  311.  * Accepts: mail stream
  312.  *        mailbox name to delete
  313.  * Returns: T on success, NIL on failure
  314.  */
  315.  
  316. long map_delete (MAILSTREAM *stream,char *mailbox)
  317. {
  318.   return map_manage (stream,mailbox,"Delete",NIL);
  319. }
  320.  
  321.  
  322. /* Mail Access Protocol rename mailbox
  323.  * Accepts: mail stream
  324.  *        old mailbox name
  325.  *        new mailbox name
  326.  * Returns: T on success, NIL on failure
  327.  */
  328.  
  329. long map_rename (MAILSTREAM *stream,char *old,char *new)
  330. {
  331.   return map_manage (stream,old,"Rename",new);
  332. }
  333.  
  334. /* Mail Access Protocol manage a mailbox
  335.  * Accepts: mail stream
  336.  *        mailbox to manipulate
  337.  *        command to execute
  338.  *        optional second argument
  339.  * Returns: T on success, NIL on failure
  340.  */
  341.  
  342. long map_manage (MAILSTREAM *stream,char *mailbox,char *command,char *arg2)
  343. {
  344.   MAILSTREAM *st = stream;
  345.   long ret;
  346.   char *s,tmp[MAILTMPLEN];
  347.   IMAPPARSEDREPLY *reply;
  348.   if (!(stream && LOCAL)) {    /* if a prototype stream requested */
  349.     if (!(stream = mail_open (NIL,mailbox,OP_HALFOPEN))) {
  350.       mm_log ("Can't access server",ERROR);
  351.       return NIL;
  352.     }
  353.   }
  354.                 /* KLUDGE: nuke host name in second argument */
  355.   if (arg2 && (*arg2 == '{') && (s = strchr (arg2,'}'))) arg2 = s + 1;
  356.                 /* get mailbox name */
  357.   mail_valid_net (mailbox,&imapdriver,NIL,tmp);
  358.                 /* send management command */
  359.   ret = imap_OK (stream,reply = imap_send (stream,command,tmp,arg2));
  360.   mm_log (reply->text, ret ? (long) NIL : ERROR);
  361.                 /* toss out temporary stream */
  362.   if (st != stream) mail_close (stream);
  363.   return ret;
  364. }
  365.  
  366. /* Mail Access Protocol open
  367.  * Accepts: stream to open
  368.  * Returns: stream to use on success, NIL on failure
  369.  */
  370.  
  371. MAILSTREAM *map_open (MAILSTREAM *stream)
  372. {
  373.   long i,j;
  374.   char username[MAILTMPLEN],pwd[MAILTMPLEN],tmp[MAILTMPLEN];
  375.   NETMBX mb;
  376.   char *s;
  377.   IMAPPARSEDREPLY *reply = NIL;
  378.                 /* return prototype for OP_PROTOTYPE call */
  379.   if (!stream) return &imapproto;
  380.   mail_valid_net_parse (stream->mailbox,&mb);
  381.                 /* default mailbox name */
  382.   if (!*mb.mailbox) strcpy (mb.mailbox,mb.bbdflag ? "general" : "INBOX");
  383.   if (LOCAL) {            /* if stream opened earlier by us */
  384.     if (strcmp (ucase (strcpy (tmp,mb.host)),
  385.         ucase (strcpy (pwd,imap_host (stream))))) {
  386.                 /* if hosts are different punt it */
  387.       sprintf (tmp,"Closing connection to %s",imap_host (stream));
  388.       if (!stream->silent) mm_log (tmp,(long) NIL);
  389.       map_close (stream);
  390.     }    
  391.     else {            /* else recycle if still alive */
  392.       i = stream->silent;    /* temporarily mark silent */
  393.       stream->silent = T;    /* don't give mm_exists() events */
  394.       j = map_ping (stream);    /* learn if stream still alive */
  395.       stream->silent = i;    /* restore prior state */
  396.       if (j) {            /* was stream still alive? */
  397.     sprintf (tmp,"Reusing connection to %s",mb.host);
  398.     if (!stream->silent) mm_log (tmp,(long) NIL);
  399.     map_do_gc (stream,GC_TEXTS);
  400.       }
  401.       else map_close (stream);
  402.     }
  403.     mail_free_cache (stream);
  404.   }
  405.  
  406.   if (!LOCAL) {            /* open new connection if no recycle */
  407.     stream->local = fs_get (sizeof (IMAPLOCAL));
  408.     LOCAL->reply.line = LOCAL->reply.tag = LOCAL->reply.key =
  409.       LOCAL->reply.text = LOCAL->prefix = NIL;
  410.     LOCAL->use_body = LOCAL->use_find = LOCAL->use_bboard =
  411.       LOCAL->use_purge = T;    /* assume maximal server */
  412.                 /* try authenticated open */
  413.     if (LOCAL->tcpstream = (stream->anonymous || mb.anoflag || mb.port) ? NIL :
  414.     tcp_aopen (mb.host,"/etc/rimapd")) {
  415.                 /* if success, see if reasonable banner */
  416.       if ((s = tcp_getline (LOCAL->tcpstream)) && (*s == '*') &&
  417.       (reply = imap_parse_reply (stream,s)) && !strcmp (reply->tag,"*"))
  418.     imap_parse_unsolicited (stream,reply);
  419.       else {            /* nuke the stream then */
  420.     if (s) fs_give ((void **) &s);
  421.     if (LOCAL->tcpstream) {
  422.       tcp_close (LOCAL->tcpstream);
  423.       LOCAL->tcpstream = NIL;
  424.     }
  425.       }
  426.     }
  427.     if (!LOCAL->tcpstream &&    /* try to open ordinary connection */
  428.     (LOCAL->tcpstream = tcp_open(mb.host,mb.port?(long)mb.port:map_port))&&
  429.     (!imap_OK (stream,reply = imap_reply (stream,NIL)))) {
  430.       mm_log (reply->text,ERROR);
  431.       map_close (stream);    /* failed, clean up */
  432.     }
  433.  
  434.     if (LOCAL && LOCAL->tcpstream && !strcmp (reply->key,"OK")) {
  435.                 /* only so many tries to login */
  436.       if (!lhostn) lhostn = cpystr (tcp_localhost (LOCAL->tcpstream));
  437.       for (i = 0; i < map_maxlogintrials; ++i) {
  438.     *pwd = 0;        /* get password */
  439.                 /* if caller wanted anonymous access */
  440.     if ((mb.anoflag || stream->anonymous) && !i) {
  441.       strcpy (username,"anonymous");
  442.       strcpy (pwd,*lhostn ? lhostn : "foo");
  443.     }
  444.     else mm_login (tcp_host (LOCAL->tcpstream),username,pwd,i);
  445.                 /* abort if he refuses to give a password */
  446.     if (*pwd == '\0') i = map_maxlogintrials;
  447.     else {            /* send "LOGIN username pwd" */
  448.       if (imap_OK (stream,reply = imap_send (stream,"LOGIN",username,
  449.                          pwd))) break;
  450.                 /* output failure and try again */
  451.       mm_log (reply->text,WARN);
  452.                 /* give up now if connection died */
  453.       if (!strcmp (reply->key,"BYE")) i = map_maxlogintrials;
  454.     }
  455.       }
  456.                 /* give up if too many failures */
  457.       if (i >=  map_maxlogintrials) {
  458.     mm_log (*pwd ? "Too many login failures":"Login aborted",ERROR);
  459.     map_close (stream);
  460.       }
  461.       else stream->anonymous = strcmp (username,"anonymous") ? NIL : T;
  462.     }
  463.                 /* failed utterly to open */
  464.     if (LOCAL && !LOCAL->tcpstream) map_close (stream);
  465.   }
  466.  
  467.   if (LOCAL) {            /* have a connection now??? */
  468.     stream->sequence++;        /* bump sequence number */
  469.                 /* prepare to update mailbox name */
  470.     fs_give ((void **) &stream->mailbox);
  471.     if (stream->halfopen ||    /* send "SELECT/EXAMINE/BBOARD mailbox" */
  472.     !imap_OK (stream,reply = imap_send (stream,mb.bbdflag ? "BBOARD" :
  473.                         (stream->readonly ? "EXAMINE" :
  474.                          "SELECT"),mb.mailbox,NIL))) {
  475.       sprintf (tmp,"{%s}<no_mailbox>",imap_host (stream));
  476.       stream->mailbox = cpystr (tmp);
  477.       if (!stream->halfopen) {    /* output error message if didn't ask for it */
  478.     mm_log (reply->text,ERROR);
  479.     stream->halfopen = T;
  480.       }
  481.                 /* make sure dummy message counts */
  482.       mail_exists (stream,(long) 0);
  483.       mail_recent (stream,(long) 0);
  484.     }
  485.     else {            /* update mailbox name */
  486.       sprintf (tmp,"%s{%s}%s",mb.bbdflag ? "*" : "",
  487.            imap_host (stream),mb.mailbox);
  488.       stream->mailbox = cpystr (tmp);
  489.       reply->text[11] = '\0';    /* note if server said it was readonly */
  490.       stream->readonly = !strcmp (ucase (reply->text),"[READ-ONLY]");
  491.     }
  492.     if (!(stream->nmsgs || stream->silent))
  493.       mm_log ("Mailbox is empty",(long) NIL);
  494.     if (stream->scache && LOCAL->use_purge &&
  495.     !strcmp (imap_send (stream,"PURGE ALWAYS",NIL,NIL)->key,"BAD"))
  496.       LOCAL->use_purge = NIL;
  497.   }
  498.                 /* give up if nuked during startup */
  499.   if (LOCAL && !LOCAL->tcpstream) map_close (stream);
  500.   return LOCAL ? stream : NIL;    /* if stream is alive, return to caller */
  501. }
  502.  
  503. /* Mail Access Protocol close
  504.  * Accepts: MAIL stream
  505.  */
  506.  
  507. void map_close (MAILSTREAM *stream)
  508. {
  509.   IMAPPARSEDREPLY *reply;
  510.   if (stream && LOCAL) {    /* send "LOGOUT" */
  511.     if (LOCAL->tcpstream &&
  512.     !imap_OK (stream,reply = imap_send (stream,"LOGOUT",NIL,NIL)))
  513.       mm_log (reply->text,WARN);
  514.                 /* close TCP connection if still open */
  515.     if (LOCAL->tcpstream) tcp_close (LOCAL->tcpstream);
  516.     LOCAL->tcpstream = NIL;
  517.                 /* free up memory */
  518.     if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
  519.     map_do_gc (stream,GC_TEXTS);/* nuke the cached strings */
  520.                 /* nuke the local data */
  521.     fs_give ((void **) &stream->local);
  522.   }
  523. }
  524.  
  525.  
  526. /* Mail Access Protocol fetch fast information
  527.  * Accepts: MAIL stream
  528.  *        sequence
  529.  *
  530.  * Generally, map_fetchstructure is preferred
  531.  */
  532.  
  533. void map_fetchfast (MAILSTREAM *stream,char *sequence)
  534. {                /* send "FETCH sequence FAST" */
  535.   IMAPPARSEDREPLY *reply;
  536.   if (!imap_OK (stream,reply = imap_send (stream,"FETCH",sequence,"FAST")))
  537.     mm_log (reply->text,ERROR);
  538. }
  539.  
  540.  
  541. /* Mail Access Protocol fetch flags
  542.  * Accepts: MAIL stream
  543.  *        sequence
  544.  */
  545.  
  546. void map_fetchflags (MAILSTREAM *stream,char *sequence)
  547. {                /* send "FETCH sequence FLAGS" */
  548.   IMAPPARSEDREPLY *reply;
  549.   if (!imap_OK (stream,reply = imap_send (stream,"FETCH",sequence,"FLAGS")))
  550.     mm_log (reply->text,ERROR);
  551. }
  552.  
  553. /* Mail Access Protocol fetch structure
  554.  * Accepts: MAIL stream
  555.  *        message # to fetch
  556.  *        pointer to return body
  557.  * Returns: envelope of this message, body returned in body value
  558.  *
  559.  * Fetches the "fast" information as well
  560.  */
  561.  
  562. ENVELOPE *map_fetchstructure (MAILSTREAM *stream,long msgno,BODY **body)
  563. {
  564.   long i = msgno;
  565.   long j = min (msgno + map_lookahead - 1,stream->nmsgs);
  566.   char seq[20];
  567.   LONGCACHE *lelt;
  568.   ENVELOPE **env;
  569.   BODY **b;
  570.   IMAPPARSEDREPLY *reply;
  571.   if (stream->scache) {        /* short cache */
  572.     if (msgno != stream->msgno){/* flush old poop if a different message */
  573.       mail_free_envelope (&stream->env);
  574.       mail_free_body (&stream->body);
  575.     }
  576.     stream->msgno = msgno;
  577.     env = &stream->env;        /* get pointers to envelope and body */
  578.     b = &stream->body;
  579.     sprintf (seq,"%ld",msgno);    /* never lookahead with a short cache */
  580.   }
  581.   else {            /* long cache */
  582.     lelt = mail_lelt (stream,msgno);
  583.     env = &lelt->env;        /* get pointers to envelope and body */
  584.     b = &lelt->body;
  585.     if (msgno != stream->nmsgs)    /* determine lookahead range */
  586.       while (i < j && !mail_lelt (stream,i+1)->env) i++;
  587.     sprintf (seq,"%ld:%ld",msgno,i);
  588.   }
  589.                 /* have the poop we need? */
  590.   if ((body && !*b && LOCAL->use_body) || !*env) {
  591.     mail_free_envelope (env);    /* flush old envelope and body */
  592.     mail_free_body (b);
  593.     if (!(body && LOCAL->use_body &&
  594.       (LOCAL->use_body =
  595.        (strcmp ((reply = imap_send(stream,"FETCH",seq,"FULL"))->key,"BAD")?
  596.         T : NIL)))) reply = imap_send (stream,"FETCH",seq,"ALL");
  597.     if (!imap_OK (stream,reply)) {
  598.       mm_log (reply->text,ERROR);
  599.       return NIL;
  600.     }
  601.   }
  602.   if (body) *body = *b;        /* return the body */
  603.   return *env;            /* return the envelope */
  604. }
  605.  
  606. /* Mail Access Protocol fetch message header
  607.  * Accepts: MAIL stream
  608.  *        message # to fetch
  609.  * Returns: message header in RFC822 format
  610.  */
  611.  
  612. char *map_fetchheader (MAILSTREAM *stream,long msgno)
  613. {
  614.   char tmp[40];
  615.   long i = msgno - 1;
  616.   IMAPPARSEDREPLY *reply;
  617.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  618.   if (!elt->data1) {        /* not if already cached */
  619.     sprintf (tmp,"FETCH %ld",msgno);
  620.     if (!imap_OK (stream,    /* send "FETCH msgno RFC822.HEADER" */
  621.           reply = imap_send (stream,tmp,elt->data2 ?
  622.                      "RFC822.HEADER" :
  623.                      "(RFC822.HEADER RFC822.TEXT)",NIL)))
  624.       mm_log (reply->text,ERROR);
  625.   }
  626.   return elt->data1 ? (char *) elt->data1 : "";
  627. }
  628.  
  629.  
  630. /* Mail Access Protocol fetch message text (body only)
  631.  * Accepts: MAIL stream
  632.  *        message # to fetch
  633.  * Returns: message text in RFC822 format
  634.  */
  635.  
  636. char *map_fetchtext (MAILSTREAM *stream,long msgno)
  637. {
  638.   char seq[20];
  639.   long i = msgno - 1;
  640.   IMAPPARSEDREPLY *reply;
  641.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  642.   if (!elt->data2) {        /* send "FETCH msgno RFC822.TEXT" */
  643.     sprintf (seq,"%ld",msgno);
  644.     if (!imap_OK (stream,reply = imap_send(stream,"FETCH",seq,"RFC822.TEXT")))
  645.       mm_log (reply->text,ERROR);
  646.   }
  647.   return elt->data2 ? (char *) elt->data2 : "";
  648. }
  649.  
  650. /* Mail Access Protocol fetch message body as a structure
  651.  * Accepts: Mail stream
  652.  *        message # to fetch
  653.  *        section specifier
  654.  *        pointer to length
  655.  * Returns: pointer to section of message body
  656.  */
  657.  
  658. char *map_fetchbody (MAILSTREAM *stream,long m,char *sec,unsigned long *len)
  659. {
  660.   BODY *b;
  661.   PART *pt;
  662.   char *s = sec;
  663.   char **ss;
  664.   unsigned long i;
  665.   char seq[40];
  666.   IMAPPARSEDREPLY *reply;
  667.   *len = 0;            /* in case failure */
  668.                 /* make sure have a body */
  669.   if (!(LOCAL->use_body && map_fetchstructure (stream,m,&b) && b)) {
  670.                 /* bodies not supported, wanted section 1? */
  671.     if (strcmp (sec,"1")) return NIL;
  672.                 /* yes, return text */
  673.     *len = strlen (s = map_fetchtext (stream,m));
  674.     return s;
  675.   }
  676.   if (!(s && *s && ((i = strtol (s,&s,10)) > 0))) return NIL;
  677.   do {                /* until find desired body part */
  678.                 /* multipart content? */
  679.     if (b->type == TYPEMULTIPART) {
  680.       pt = b->contents.part;    /* yes, find desired part */
  681.       while (--i && (pt = pt->next));
  682.       if (!pt) return NIL;    /* bad specifier */
  683.                 /* note new body, check valid nesting */
  684.       if (((b = &pt->body)->type == TYPEMULTIPART) && !*s) return NIL;
  685.     }
  686.     else if (i != 1) return NIL;/* otherwise must be section 1 */
  687.                 /* need to go down further? */
  688.     if (i = *s) switch (b->type) {
  689.     case TYPEMESSAGE:        /* embedded message, calculate new base */
  690.       b = b->contents.msg.body;    /* get its body, drop into multipart case */
  691.     case TYPEMULTIPART:        /* multipart, get next section */
  692.       if ((*s++ == '.') && (i = strtol (s,&s,10)) > 0) break;
  693.     default:            /* bogus subpart specification */
  694.       return NIL;
  695.     }
  696.   } while (i);
  697.  
  698.                 /* lose if body bogus */
  699.   if ((!b) || b->type == TYPEMULTIPART) return NIL;
  700.   switch (b->type) {        /* decide where the data is based on type */
  701.   case TYPEMESSAGE:        /* encapsulated message */
  702.     ss = &b->contents.msg.text;
  703.     break;
  704.   case TYPETEXT:        /* textual data */
  705.     ss = (char **) &b->contents.text;
  706.     break;
  707.   default:            /* otherwise assume it is binary */
  708.     ss = (char **) &b->contents.binary;
  709.     break;
  710.   }
  711.   if (!*ss) {            /* fetch data if don't have it yet */
  712.     sprintf (seq,"%ld BODY[%s]",m,sec);
  713.     if (!imap_OK (stream,reply = imap_send (stream,"FETCH",seq,NIL)))
  714.       mm_log (reply->text,ERROR);
  715.   }
  716.                 /* return data size if have data */
  717.   if (s = *ss) *len = b->size.bytes;
  718.   return s;
  719. }
  720.  
  721. /* Mail Access Protocol set flag
  722.  * Accepts: MAIL stream
  723.  *        sequence
  724.  *        flag(s)
  725.  */
  726.  
  727. void map_setflag (MAILSTREAM *stream,char *sequence,char *flag)
  728. {
  729.   char *tmp = (char *) fs_get (20 + strlen (sequence));
  730.   IMAPPARSEDREPLY *reply;
  731.                 /* "STORE sequence +Flags flag" */
  732.   sprintf (tmp,"STORE %s +Flags",sequence);
  733.   if (!imap_OK (stream,reply = imap_send (stream,tmp,flag,NIL)))
  734.     mm_log (reply->text,ERROR);
  735.   fs_give ((void **) &tmp);
  736. }
  737.  
  738.  
  739. /* Mail Access Protocol clear flag
  740.  * Accepts: MAIL stream
  741.  *        sequence
  742.  *        flag(s)
  743.  */
  744.  
  745. void map_clearflag (MAILSTREAM *stream,char *sequence,char *flag)
  746. {
  747.   char *tmp = (char *) fs_get (20 + strlen (sequence));
  748.   IMAPPARSEDREPLY *reply;
  749.                 /* "STORE sequence -Flags flag" */
  750.   sprintf (tmp,"STORE %s -Flags",sequence);
  751.   if (!imap_OK (stream,reply = imap_send (stream,tmp,flag,NIL)))
  752.     mm_log (reply->text,ERROR);
  753.   fs_give ((void **) &tmp);
  754. }
  755.  
  756. /* Mail Access Protocol search for messages
  757.  * Accepts: MAIL stream
  758.  *        search criteria
  759.  */
  760.  
  761. void map_search (MAILSTREAM *stream,char *criteria)
  762. {
  763.   long i,j;
  764.   char *s;
  765.   IMAPPARSEDREPLY *reply;
  766.   MESSAGECACHE *elt;
  767.                 /* do the SEARCH */
  768.   if (imap_OK (stream,reply = imap_send (stream,"SEARCH",criteria,NIL))) {
  769.     if (stream->scache) return;    /* can never pre-fetch with a short cache */
  770.     s = LOCAL->tmp;        /* build sequence in temporary buffer */
  771.     *s = '\0';            /* initially nothing */
  772.                 /* search through mailbox */
  773.     for (i = 1; i <= stream->nmsgs; ++i) 
  774.                 /* for searched messages with no envelope */
  775.       if ((elt = mail_elt (stream,i)) && elt->searched &&
  776.       !mail_lelt (stream,i)->env) {
  777.                 /* prepend with comma if not first time */
  778.     if (LOCAL->tmp[0]) *s++ = ',';
  779.     sprintf (s,"%ld",j = i);/* output message number */
  780.     s += strlen (s);    /* point at end of string */
  781.                 /* search for possible end of range */
  782.     while (i < stream->nmsgs && (elt = mail_elt (stream,i+1)) &&
  783.            elt->searched && !mail_lelt (stream,i+1)->env) i++;
  784.     if (i != j) {        /* if a range */
  785.       sprintf (s,":%ld",i);    /* output delimiter and end of range */
  786.       s += strlen (s);    /* point at end of string */
  787.     }
  788.       }
  789.     if (LOCAL->tmp[0]) {    /* anything to pre-fetch? */
  790.       s = cpystr (LOCAL->tmp);    /* yes, copy sequence */
  791.       if (!imap_OK (stream,reply = imap_send (stream,"FETCH",s,"ALL")))
  792.     mm_log (reply->text,ERROR);
  793.       fs_give ((void **) &s);    /* flush copy of sequence */
  794.     }
  795.   }
  796.   else mm_log (reply->text,ERROR);
  797. }
  798.  
  799. /* Mail Access Protocol ping mailbox
  800.  * Accepts: MAIL stream
  801.  * Returns: T if stream still alive, else NIL
  802.  */
  803.  
  804. long map_ping (MAILSTREAM *stream)
  805. {
  806.   return (LOCAL->tcpstream &&    /* send "NOOP" */
  807.       imap_OK (stream,imap_send (stream,"NOOP",NIL,NIL))) ? T : NIL;
  808. }
  809.  
  810.  
  811. /* Mail Access Protocol check mailbox
  812.  * Accepts: MAIL stream
  813.  */
  814.  
  815. void map_check (MAILSTREAM *stream)
  816. {
  817.                 /* send "CHECK" */
  818.   IMAPPARSEDREPLY *reply = imap_send (stream,"CHECK",NIL,NIL);
  819.   mm_log (reply->text,imap_OK (stream,reply) ? (long) NIL : ERROR);
  820. }
  821.  
  822.  
  823. /* Mail Access Protocol expunge mailbox
  824.  * Accepts: MAIL stream
  825.  */
  826.  
  827. void map_expunge (MAILSTREAM *stream)
  828. {
  829.                 /* send "EXPUNGE" */
  830.   IMAPPARSEDREPLY *reply = imap_send (stream,"EXPUNGE",NIL,NIL);
  831.   mm_log (reply->text,imap_OK (stream,reply) ? (long) NIL : ERROR);
  832. }
  833.  
  834. /* Mail Access Protocol copy message(s)
  835.  * Accepts: MAIL stream
  836.  *        sequence
  837.  *        destination mailbox
  838.  * Returns: T if successful else NIL
  839.  */
  840.  
  841. long map_copy (MAILSTREAM *stream,char *sequence,char *mailbox)
  842. {
  843.   IMAPPARSEDREPLY *reply;
  844.   if (!LOCAL->tcpstream) {    /* not valid on dead stream */
  845.     mm_log ("Copy rejected: connection to remote IMAP server closed",ERROR);
  846.     return NIL;
  847.   }
  848.                 /* send "COPY sequence mailbox" */
  849.   if (!imap_OK (stream,reply = imap_send (stream,"COPY",sequence,mailbox))) {
  850.     mm_log (reply->text,ERROR);
  851.     return NIL;
  852.   }
  853.   map_setflag (stream,sequence,"\\Seen");
  854.   return T;
  855. }
  856.  
  857.  
  858. /* Mail Access Protocol move message(s)
  859.  * Accepts: MAIL stream
  860.  *        sequence
  861.  *        destination mailbox
  862.  * Returns: T if successful else NIL
  863.  */
  864.  
  865. long map_move (MAILSTREAM *stream,char *sequence,char *mailbox)
  866. {
  867.   IMAPPARSEDREPLY *reply;
  868.   if (!LOCAL->tcpstream) {    /* not valid on dead stream */
  869.     mm_log ("Move rejected: connection to remote IMAP server closed",ERROR);
  870.     return NIL;
  871.   }
  872.                 /* send "COPY sequence mailbox" */
  873.   if (!imap_OK (stream,reply = imap_send (stream,"COPY",sequence,mailbox))) {
  874.     mm_log (reply->text,ERROR);
  875.     return NIL;
  876.   }
  877.   map_setflag (stream,sequence,"\\Deleted \\Seen");
  878.   return T;
  879. }
  880.  
  881. /* Mail Access Protocol append message string
  882.  * Accepts: mail stream
  883.  *        destination mailbox
  884.  *        stringstruct of message to append
  885.  * Returns: T on success, NIL on failure
  886.  */
  887.  
  888. long map_append (MAILSTREAM *stream,char *mailbox,STRING *message)
  889. {
  890.   MAILSTREAM *st = stream;
  891.   long ret;
  892.   char tmp[MAILTMPLEN];
  893.   IMAPPARSEDREPLY *reply;
  894.                 /* in case useful stream not given */
  895.   if (!(stream && LOCAL && LOCAL->tcpstream)) {
  896.     if (!(stream = mail_open (NIL,mailbox,OP_HALFOPEN))) {
  897.       mm_log ("Can't access server for append",ERROR);
  898.       return NIL;
  899.     }
  900.   }
  901.                 /* get mailbox name */
  902.   if ((ret = mail_valid_net (mailbox,&imapdriver,NIL,tmp) ? T : NIL) &&
  903.       (!imap_OK (stream,    /* report error if it choked */
  904.          reply = imap_send_literal (stream,"APPEND",tmp,message)))) {
  905.     mm_log (reply->text,ERROR);
  906.     ret = NIL;
  907.   }
  908.                 /* toss out temporary stream */
  909.   if (st != stream) mail_close (stream);
  910.   return ret;            /* return */
  911. }
  912.  
  913. /* Mail Access Protocol garbage collect stream
  914.  * Accepts: Mail stream
  915.  *        garbage collection flags
  916.  */
  917.  
  918. void map_gc (MAILSTREAM *stream,long gcflags)
  919. {
  920.   char tmp[MAILTMPLEN];
  921.   if (stream->nmsgs) {        /* nothing to purge if no messages */
  922.     sprintf (tmp,"1:%ld",stream->nmsgs);
  923.     if (LOCAL->use_purge && (gcflags & GC_ELT) &&
  924.     !strcmp (imap_send (stream,"PURGE STATUS",tmp,NIL)->key,"BAD"))
  925.       LOCAL->use_purge = NIL;
  926.     if (LOCAL->use_purge && (gcflags & GC_ENV) &&
  927.     !strcmp (imap_send (stream,"PURGE STRUCTURE",tmp,NIL)->key,"BAD"))
  928.       LOCAL->use_purge = NIL;
  929.     if (LOCAL->use_purge && (gcflags & GC_TEXTS) &&
  930.     !strcmp (imap_send (stream,"PURGE TEXTS",tmp,NIL)->key,"BAD"))
  931.       LOCAL->use_purge = NIL;
  932.   }
  933.   map_do_gc (stream,gcflags);    /* now call our worker routine */
  934. }
  935.  
  936.  
  937. /* Mail Access Protocol garbage collect stream worker routine
  938.  * Accepts: Mail stream
  939.  *        garbage collection flags
  940.  */
  941.  
  942. void map_do_gc (MAILSTREAM *stream,long gcflags)
  943. {
  944.   unsigned long i;
  945.   MESSAGECACHE *elt;
  946.   LONGCACHE *lelt;
  947.                 /* make sure the cache is large enough */
  948.   (*mailcache) (stream,stream->nmsgs,CH_SIZE);
  949.   if (gcflags & GC_TEXTS) {    /* garbage collect texts? */
  950.     for (i = 1; i <= stream->nmsgs; ++i)
  951.       if (elt = (MESSAGECACHE *) (*mailcache) (stream,i,CH_ELT)) {
  952.     if (elt->data1) fs_give ((void **) &elt->data1);
  953.     if (elt->data2) fs_give ((void **) &elt->data2);
  954.     if (!stream->scache) map_gc_body ((lelt = mail_lelt (stream,i))->body);
  955.       }
  956.     map_gc_body (stream->body);    /* free texts from short cache body */
  957.   }
  958.                 /* gc cache if requested and unlocked */
  959.   if (gcflags & GC_ELT) for (i = 1; i <= stream->nmsgs; ++i)
  960.     if ((elt = (MESSAGECACHE *) (*mailcache) (stream,i,CH_ELT)) &&
  961.     (elt->lockcount == 1)) (*mailcache) (stream,i,CH_FREE);
  962. }
  963.  
  964. /* Mail Access Protocol garbage collect body texts
  965.  * Accepts: body to GC
  966.  */
  967.  
  968. void map_gc_body (BODY *body)
  969. {
  970.   PART *part;
  971.   if (body) switch (body->type) {
  972.   case TYPETEXT:        /* unformatted text */
  973.     if (body->contents.text) fs_give ((void **) &body->contents.text);
  974.     break;
  975.   case TYPEMULTIPART:        /* multiple part */
  976.     if (part = body->contents.part) do map_gc_body (&part->body);
  977.     while (part = part->next);
  978.     break;
  979.   case TYPEMESSAGE:        /* encapsulated message */
  980.     map_gc_body (body->contents.msg.body);
  981.     if (body->contents.msg.text)
  982.       fs_give ((void **) &body->contents.msg.text);
  983.     break;
  984.   case TYPEAPPLICATION:        /* application data */
  985.   case TYPEAUDIO:        /* audio */
  986.   case TYPEIMAGE:        /* static image */
  987.   case TYPEVIDEO:        /* video */
  988.     if (body->contents.binary) fs_give (&body->contents.binary);
  989.     break;
  990.   default:
  991.     break;
  992.   }
  993. }
  994.  
  995. /* Internal routines */
  996.  
  997.  
  998. /* Mail Access Protocol return host name
  999.  * Accepts: MAIL stream
  1000.  * Returns: host name
  1001.  */
  1002.  
  1003. char *imap_host (MAILSTREAM *stream)
  1004. {
  1005.                 /* return host name on stream if open */
  1006.   return (LOCAL && LOCAL->tcpstream) ? tcp_host (LOCAL->tcpstream) :
  1007.     "<closed stream>";
  1008. }
  1009.  
  1010. /* Mail Access Protocol send command
  1011.  * Accepts: MAIL stream
  1012.  *        command
  1013.  *        command argument
  1014.  *        second command argument
  1015.  * Returns: parsed reply
  1016.  */
  1017.  
  1018. IMAPPARSEDREPLY *imap_send (MAILSTREAM *stream,char *cmd,char *arg,char *arg2)
  1019. {
  1020.   IMAPPARSEDREPLY *reply = NIL;
  1021.   if (arg2 && strpbrk (arg2,"\012\015\"%{\\")) {
  1022.     STRING s;
  1023.     INIT (&s,mail_string,(void *) arg2,(unsigned long) strlen (arg2));
  1024.     reply = imap_send_literal (stream,cmd,arg,&s);
  1025.   }
  1026.   else {
  1027.     char tag[7];
  1028.                   /* gensym a new tag */
  1029.     sprintf (tag,"A%05ld",stream->gensym++);
  1030.     if (!LOCAL->tcpstream) return imap_fake(stream,tag,"OK No-op dead stream");
  1031.                 /* begin command */
  1032.     sprintf (LOCAL->tmp,"%s %s",tag,cmd);
  1033.     mail_lock (stream);        /* lock up the stream */
  1034.     if (arg) {            /* argument present? */
  1035.       sprintf (LOCAL->tmp + strlen (LOCAL->tmp)," %s",arg);
  1036.                 /* second argument present? */
  1037.       if (arg2) sprintf (LOCAL->tmp + strlen (LOCAL->tmp),
  1038.              strchr (arg2,' ') ? " \"%s\"" : " %s",arg2);
  1039.     }
  1040.                 /* output to debugging telemetry */
  1041.     if (stream->debug) mm_dlog (LOCAL->tmp);
  1042.     strcat (LOCAL->tmp,"\015\012");
  1043.                 /* send the command */
  1044.     if (tcp_soutr (LOCAL->tcpstream,LOCAL->tmp))
  1045.       reply = imap_reply (stream,tag);
  1046.     mail_unlock (stream);    /* unlock stream */
  1047.     if (!reply) {        /* close TCP connection if it died */
  1048.       tcp_close (LOCAL->tcpstream);
  1049.       LOCAL->tcpstream = NIL;
  1050.       return imap_fake (stream,tag,"BYE IMAP connection broken in send");
  1051.     }
  1052.   }
  1053.   return reply;            /* return reply to caller */
  1054. }
  1055.  
  1056. /* Mail Access Protocol send command with literal argument
  1057.  * Accepts: MAIL stream
  1058.  *        command
  1059.  *        command argument
  1060.  *        second command argument
  1061.  * Returns: parsed reply
  1062.  */
  1063.  
  1064. IMAPPARSEDREPLY *imap_send_literal (MAILSTREAM *stream,char *cmd,char *arg,
  1065.                     STRING *arg2)
  1066. {
  1067.   char tag[7];
  1068.   char *s = NIL;
  1069.   unsigned long i = SIZE (arg2);
  1070.   IMAPPARSEDREPLY *reply = NIL;
  1071.   if (!LOCAL->tcpstream) return imap_fake (stream,tag,"OK No-op dead stream");
  1072.                   /* gensym a new tag */
  1073.   sprintf (tag,"A%05ld",stream->gensym++);
  1074.                 /* begin command */
  1075.   sprintf (LOCAL->tmp,"%s %s %s {%ld}",tag,cmd,arg,i);
  1076.   mail_lock (stream);        /* lock up the stream */
  1077.                 /* output debugging telemetry */
  1078.   if (stream->debug) mm_dlog (LOCAL->tmp);
  1079.   strcat (LOCAL->tmp,"\015\012");
  1080.                 /* send the command */
  1081.   if (tcp_soutr (LOCAL->tcpstream,LOCAL->tmp) &&
  1082.       !strcmp ((reply = imap_reply (stream,tag))->tag,"+")) {
  1083.                 /* dump the message */
  1084.     while (i && tcp_sout (LOCAL->tcpstream,arg2->curpos,arg2->cursize)) {
  1085.       i -= arg2->cursize;    /* note that we wrote out this much */
  1086.       arg2->curpos += (arg2->cursize - 1);
  1087.       arg2->cursize = 1;
  1088.       SNX(arg2);        /* advance to next buffer's worth */
  1089.     }
  1090.     if ((!i) && tcp_soutr (LOCAL->tcpstream,"\015\012"))
  1091.       reply = imap_reply (stream,tag);
  1092.     else reply = NIL;
  1093.   }
  1094.   mail_unlock (stream);        /* unlock stream */
  1095.   if (!reply) {            /* close TCP connection if it died */
  1096.     tcp_close (LOCAL->tcpstream);
  1097.     LOCAL->tcpstream = NIL;
  1098.     return imap_fake (stream,tag,"BYE IMAP connection broken in send");
  1099.   }
  1100.   return reply;            /* return reply to caller */
  1101. }
  1102.  
  1103. /* Mail Access Protocol get reply
  1104.  * Accepts: MAIL stream
  1105.  *        tag to search or NIL if want a greeting
  1106.  * Returns: parsed reply, never NIL
  1107.  */
  1108.  
  1109. IMAPPARSEDREPLY *imap_reply (MAILSTREAM *stream,char *tag)
  1110. {
  1111.   IMAPPARSEDREPLY *reply;
  1112.   while (LOCAL->tcpstream) {    /* parse reply from server */
  1113.     if (reply = imap_parse_reply (stream,tcp_getline (LOCAL->tcpstream))) {
  1114.                 /* untagged response means unsolicited data */
  1115.       if (!strcmp (reply->tag,"*")) {
  1116.     imap_parse_unsolicited (stream,reply);
  1117.     if (tag) continue;    /* waiting for a response */
  1118.     return reply;        /* return greeting */
  1119.       }
  1120.       else {            /* not unsolicited reponse */
  1121.     if (tag && ((!strcmp (tag,reply->tag)) || (!strcmp (reply->tag,"+"))))
  1122.       return reply;        /* return if desired tag or + */
  1123.                 /* report bogon */
  1124.     sprintf (LOCAL->tmp,"Unexpected tagged response: %.80s %.80s %.80s",
  1125.          reply->tag,reply->key,reply->text);
  1126.     mm_log (LOCAL->tmp,WARN);
  1127.       }
  1128.     }
  1129.   }
  1130.   return imap_fake (stream,tag,"BYE IMAP connection broken in reply");
  1131. }
  1132.  
  1133. /* Mail Access Protocol parse reply
  1134.  * Accepts: MAIL stream
  1135.  *        text of reply
  1136.  * Returns: parsed reply, or NIL if can't parse at least a tag and key
  1137.  */
  1138.  
  1139.  
  1140. IMAPPARSEDREPLY *imap_parse_reply (MAILSTREAM *stream,char *text)
  1141. {
  1142.   if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
  1143.   if (!(LOCAL->reply.line = text)) {
  1144.                 /* NIL text means the stream died */
  1145.     tcp_close (LOCAL->tcpstream);
  1146.     LOCAL->tcpstream = NIL;
  1147.     return NIL;
  1148.   }
  1149.   if (stream->debug) mm_dlog (LOCAL->reply.line);
  1150.   LOCAL->reply.key = NIL;    /* init fields in case error */
  1151.   LOCAL->reply.text = NIL;
  1152.                 /* parse separate tag, key, text */
  1153.   if (!((LOCAL->reply.tag = (char *) strtok (LOCAL->reply.line," ")) &&
  1154.     (LOCAL->reply.key = (char *) strtok (NIL," ")))) {
  1155.                 /* determine what is missing */
  1156.     if (!LOCAL->reply.tag) strcpy (LOCAL->tmp,"IMAP server sent a blank line");
  1157.     else sprintf (LOCAL->tmp,"Missing IMAP reply key: %.80s",LOCAL->reply.tag);
  1158.     mm_log (LOCAL->tmp,WARN);    /* pass up the barfage */
  1159.     return NIL;            /* can't parse this text */
  1160.   }
  1161.   ucase (LOCAL->reply.key);    /* make sure key is upper case */
  1162.                 /* get text as well, allow empty text */
  1163.   if (!(LOCAL->reply.text = (char *) strtok (NIL,"\n")))
  1164.     LOCAL->reply.text = LOCAL->reply.key + strlen (LOCAL->reply.key);
  1165.   return &LOCAL->reply;        /* return parsed reply */
  1166. }
  1167.  
  1168. /* Mail Access Protocol fake reply
  1169.  * Accepts: MAIL stream
  1170.  *        tag
  1171.  *        text of fake reply
  1172.  * Returns: parsed reply
  1173.  */
  1174.  
  1175. IMAPPARSEDREPLY *imap_fake (MAILSTREAM *stream,char *tag,char *text)
  1176. {
  1177.                 /* build fake reply string */
  1178.   sprintf (LOCAL->tmp,"%s %s",tag,text);
  1179.                 /* parse and return it */
  1180.   return imap_parse_reply (stream,cpystr (LOCAL->tmp));
  1181. }
  1182.  
  1183.  
  1184. /* Mail Access Protocol check for OK response in tagged reply
  1185.  * Accepts: MAIL stream
  1186.  *        parsed reply
  1187.  * Returns: T if OK else NIL
  1188.  */
  1189.  
  1190. long imap_OK (MAILSTREAM *stream,IMAPPARSEDREPLY *reply)
  1191. {
  1192.                 /* OK - operation succeeded */
  1193.   if (!strcmp (reply->key,"OK") ||
  1194.       (!strcmp (reply->tag,"*") && !strcmp (reply->key,"PREAUTH")))
  1195.     return T;
  1196.                 /* NO - operation failed */
  1197.   else if (strcmp (reply->key,"NO")) {
  1198.                 /* BAD - operation rejected */
  1199.     if (!strcmp (reply->key,"BAD"))
  1200.       sprintf (LOCAL->tmp,"IMAP error: %.80s",reply->text);
  1201.                 /* BYE - server is going away */
  1202.     else if (!strcmp (reply->key,"BYE")) strcpy (LOCAL->tmp,reply->text);
  1203.     else sprintf (LOCAL->tmp,"Unexpected IMAP response: %.80s %.80s",
  1204.           reply->key,reply->text);
  1205.     mm_log (LOCAL->tmp,WARN);    /* log the sucker */
  1206.   }
  1207.   return NIL;
  1208. }
  1209.  
  1210. /* Mail Access Protocol parse and act upon unsolicited reply
  1211.  * Accepts: MAIL stream
  1212.  *        parsed reply
  1213.  */
  1214.  
  1215. void imap_parse_unsolicited (MAILSTREAM *stream,IMAPPARSEDREPLY *reply)
  1216. {
  1217.   long msgno;
  1218.   char *s;
  1219.   char *keyptr,*txtptr;
  1220.                 /* see if key is a number */
  1221.   msgno = strtol (reply->key,&s,10);
  1222.   if (*s) {            /* if non-numeric */
  1223.     if (!strcmp (reply->key,"FLAGS")) imap_parse_flaglst (stream,reply);
  1224.     else if (!strcmp (reply->key,"SEARCH")) imap_searched (stream,reply->text);
  1225.     else if (!strcmp (reply->key,"MAILBOX")) {
  1226.       sprintf (LOCAL->tmp,"%s%s",LOCAL->prefix ? LOCAL->prefix:"",reply->text);
  1227.       mm_mailbox (LOCAL->tmp);
  1228.     }
  1229.     else if (!strcmp (reply->key,"BBOARD")) {
  1230.       sprintf (LOCAL->tmp,"%s%s",LOCAL->prefix ? LOCAL->prefix:"",reply->text);
  1231.       mm_bboard (LOCAL->tmp);
  1232.     }
  1233.     else if (!strcmp (reply->key,"BYE")) {
  1234.       if (!stream->silent) mm_log (reply->text,(long) NIL);
  1235.     }
  1236.     else if (!strcmp (reply->key,"OK") || !strcmp (reply->key,"PREAUTH")) {
  1237.                 /* note if server said it was readonly */
  1238.       strncpy (LOCAL->tmp,reply->text,11);
  1239.       LOCAL->tmp[11] = '\0';    /* tie off text */
  1240.       if (strcmp (ucase (LOCAL->tmp),"[READ-ONLY]")) s = reply->text;
  1241.       else {
  1242.     stream->readonly = T;    /* make readonly now */
  1243.     s = reply->text + 12;    /* skip the cookie */
  1244.       }
  1245.       mm_notify (stream,s,(long) NIL);
  1246.     }
  1247.     else if (!strcmp (reply->key,"NO")) {
  1248.       if (!stream->silent) mm_notify (stream,reply->text,WARN);
  1249.     }
  1250.     else if (!strcmp (reply->key,"BAD")) mm_notify (stream,reply->text,ERROR);
  1251.     else {
  1252.       sprintf (LOCAL->tmp,"Unexpected unsolicited message: %.80s",reply->key);
  1253.       mm_log (LOCAL->tmp,WARN);
  1254.     }
  1255.   }
  1256.  
  1257.   else {            /* if numeric, a keyword follows */
  1258.                 /* deposit null at end of keyword */
  1259.     keyptr = ucase ((char *) strtok (reply->text," "));
  1260.                 /* and locate the text after it */
  1261.     txtptr = (char *) strtok (NIL,"\n");
  1262.                 /* now take the action */
  1263.                 /* change in size of mailbox */
  1264.     if (!strcmp (keyptr,"EXISTS")) mail_exists (stream,msgno);
  1265.     else if (!strcmp (keyptr,"RECENT")) mail_recent (stream,msgno);
  1266.     else if (!strcmp (keyptr,"EXPUNGE")) imap_expunged (stream,msgno);
  1267.     else if (!strcmp (keyptr,"FETCH"))
  1268.       imap_parse_data (stream,msgno,txtptr,reply);
  1269.                 /* obsolete alias for FETCH */
  1270.     else if (!strcmp (keyptr,"STORE"))
  1271.       imap_parse_data (stream,msgno,txtptr,reply);
  1272.                 /* obsolete response to COPY */
  1273.     else if (strcmp (keyptr,"COPY")) {
  1274.       sprintf (LOCAL->tmp,"Unknown message data: %ld %.80s",msgno,keyptr);
  1275.       mm_log (LOCAL->tmp,WARN);
  1276.     }
  1277.   }
  1278. }
  1279.  
  1280. /* Mail Access Protocol parse flag list
  1281.  * Accepts: MAIL stream
  1282.  *        parsed reply
  1283.  *
  1284.  *  The reply->line is yanked out of the parsed reply and stored on
  1285.  * stream->flagstring.  This is the original fs_get'd reply string, and
  1286.  * has all the flagstrings embedded in it.
  1287.  */
  1288.  
  1289. void imap_parse_flaglst (MAILSTREAM *stream,IMAPPARSEDREPLY *reply)
  1290. {
  1291.   char *text = reply->text;
  1292.   char *flag;
  1293.   long i;
  1294.                 /* flush old flagstring and flags if any */
  1295.   if (stream->flagstring) fs_give ((void **) &stream->flagstring);
  1296.   for (i = 0; i < NUSERFLAGS; ++i) stream->user_flags[i] = NIL;
  1297.                 /* remember this new one */
  1298.   stream->flagstring = reply->line;
  1299.   reply->line = NIL;
  1300.   ++text;            /* skip past open parenthesis */
  1301.                 /* get first flag if any */
  1302.   if (flag = (char *) strtok (text," )")) {
  1303.     i = 0;            /* init flag index */
  1304.                 /* add all user flags */
  1305.     do if (*flag != '\\') stream->user_flags[i++] = flag;
  1306.       while (flag = (char *) strtok (NIL," )"));
  1307.   }
  1308. }
  1309.  
  1310.  
  1311. /* Mail Access Protocol messages have been searched out
  1312.  * Accepts: MAIL stream
  1313.  *        list of message numbers
  1314.  *
  1315.  * Calls external "mail_searched" function to notify main program
  1316.  */
  1317.  
  1318. void imap_searched (MAILSTREAM *stream,char *text)
  1319. {
  1320.                 /* only do something if have text */
  1321.   if (text && (text = (char *) strtok (text," "))) 
  1322.     for (; text; text = (char *) strtok (NIL," "))
  1323.       mail_searched (stream,atol (text));
  1324. }
  1325.  
  1326. /* Mail Access Protocol message has been expunged
  1327.  * Accepts: MAIL stream
  1328.  *        message number
  1329.  *
  1330.  * Calls external "mail_searched" function to notify main program
  1331.  */
  1332.  
  1333. void imap_expunged (MAILSTREAM *stream,long msgno)
  1334. {
  1335.   MESSAGECACHE *elt = (MESSAGECACHE *) (*mailcache) (stream,msgno,CH_ELT);
  1336.   if (elt) {
  1337.     if (elt->data1) fs_give ((void **) &elt->data1);
  1338.     if (elt->data2) fs_give ((void **) &elt->data2);
  1339.   }
  1340.   mail_expunged (stream,msgno);    /* notify upper level */
  1341. }
  1342.  
  1343.  
  1344. /* Mail Access Protocol parse data
  1345.  * Accepts: MAIL stream
  1346.  *        message #
  1347.  *        text to parse
  1348.  *        parsed reply
  1349.  *
  1350.  *  This code should probably be made a bit more paranoid about malformed
  1351.  * S-expressions.
  1352.  */
  1353.  
  1354. void imap_parse_data (MAILSTREAM *stream,long msgno,char *text,
  1355.               IMAPPARSEDREPLY *reply)
  1356. {
  1357.   char *prop;
  1358.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1359.   ++text;            /* skip past open parenthesis */
  1360.                 /* parse Lisp-form property list */
  1361.   while (prop = (char *) strtok (text," )")) {
  1362.                 /* point at value */
  1363.     text = (char *) strtok (NIL,"\n");
  1364.                 /* parse the property and its value */
  1365.     imap_parse_prop (stream,elt,ucase (prop),&text,reply);
  1366.   }
  1367. }
  1368.  
  1369. /* Mail Access Protocol parse property
  1370.  * Accepts: MAIL stream
  1371.  *        cache item
  1372.  *        property name
  1373.  *        property value text pointer
  1374.  *        parsed reply
  1375.  */
  1376.  
  1377. void imap_parse_prop (MAILSTREAM *stream,MESSAGECACHE *elt,char *prop,
  1378.               char **txtptr,IMAPPARSEDREPLY *reply)
  1379. {
  1380.   char *s;
  1381.   ENVELOPE **env;
  1382.   BODY **body;
  1383.   long i = elt->msgno - 1;
  1384.   if (!strcmp (prop,"ENVELOPE")) {
  1385.     if (stream->scache) {    /* short cache, flush old stuff */
  1386.       mail_free_envelope (&stream->env);
  1387.       mail_free_body (&stream->body);
  1388.       stream->msgno =elt->msgno;/* set new current message number */
  1389.       env = &stream->env;    /* get pointer to envelope */
  1390.     }
  1391.     else env = &mail_lelt (stream,elt->msgno)->env;
  1392.     imap_parse_envelope (stream,env,txtptr,reply);
  1393.   }
  1394.   else if (!strcmp (prop,"FLAGS")) imap_parse_flags (stream,elt,txtptr);
  1395.   else if (!strcmp (prop,"INTERNALDATE")) {
  1396.     if (s = imap_parse_string (stream,txtptr,reply,(long) NIL)) {
  1397.       if (!mail_parse_date (elt,s)) {
  1398.     sprintf (LOCAL->tmp,"Bogus date: %.80s",s);
  1399.     mm_log (LOCAL->tmp,WARN);
  1400.       }
  1401.       fs_give ((void **) &s);
  1402.     }
  1403.   }
  1404.   else if (!strcmp (prop,"RFC822.HEADER")) {
  1405.     if (elt->data1) fs_give ((void **) &elt->data1);
  1406.     elt->data1 = (unsigned long) imap_parse_string (stream,txtptr,reply,
  1407.                             elt->msgno);
  1408.   }
  1409.  
  1410.   else if (!strcmp (prop,"RFC822.SIZE"))
  1411.     elt->rfc822_size = imap_parse_number (stream,txtptr);
  1412.   else if (!strcmp (prop,"RFC822.TEXT")) {
  1413.     if (elt->data2) fs_give ((void **) &elt->data2);
  1414.     elt->data2 = (unsigned long) imap_parse_string (stream,txtptr,reply,
  1415.                             elt->msgno);
  1416.   }
  1417.   else if (prop[0] == 'B' && prop[1] == 'O' && prop[2] == 'D' &&
  1418.        prop[3] == 'Y') {
  1419.     s = cpystr (prop+4);    /* copy segment specifier */
  1420.     if (stream->scache) {    /* short cache, flush old stuff */
  1421.       if (elt->msgno != stream->msgno) {
  1422.                 /* losing real bad here */
  1423.     mail_free_envelope (&stream->env);
  1424.     mail_free_body (&stream->body);
  1425.     sprintf (LOCAL->tmp,"Body received for %ld when current is %ld",
  1426.          elt->msgno,stream->msgno);
  1427.     mm_log (LOCAL->tmp,WARN);
  1428.     stream->msgno = elt->msgno;
  1429.       }
  1430.       body = &stream->body;    /* get pointer to body */
  1431.     }
  1432.     else body = &mail_lelt (stream,elt->msgno)->body;
  1433.     imap_parse_body (stream,elt->msgno,body,s,txtptr,reply);
  1434.     fs_give ((void **) &s);
  1435.   }
  1436.                 /* this shouldn't happen with our client */
  1437.   else if (!strcmp (prop,"RFC822")) {
  1438.     if (elt->data2) fs_give ((void **) &elt->data2);
  1439.     elt->data2 = (unsigned long) imap_parse_string (stream,txtptr,reply,
  1440.                             elt->msgno);
  1441.   }
  1442.   else {
  1443.     sprintf (LOCAL->tmp,"Unknown message property: %.80s",prop);
  1444.     mm_log (LOCAL->tmp,WARN);
  1445.   }
  1446. }
  1447.  
  1448. /* Mail Access Protocol parse envelope
  1449.  * Accepts: MAIL stream
  1450.  *        pointer to envelope pointer
  1451.  *        current text pointer
  1452.  *        parsed reply
  1453.  *
  1454.  * Updates text pointer
  1455.  */
  1456.  
  1457. void imap_parse_envelope (MAILSTREAM *stream,ENVELOPE **env,char **txtptr,
  1458.               IMAPPARSEDREPLY *reply)
  1459. {
  1460.   char c = *((*txtptr)++);    /* grab first character */
  1461.                 /* ignore leading spaces */
  1462.   while (c == ' ') c = *((*txtptr)++);
  1463.                 /* free any old envelope */
  1464.   if (*env) mail_free_envelope (env);
  1465.   switch (c) {            /* dispatch on first character */
  1466.   case '(':            /* if envelope S-expression */
  1467.     *env = mail_newenvelope ();    /* parse the new envelope */
  1468.     (*env)->date = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1469.     (*env)->subject = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1470.     (*env)->from = imap_parse_adrlist (stream,txtptr,reply);
  1471.     (*env)->sender = imap_parse_adrlist (stream,txtptr,reply);
  1472.     (*env)->reply_to = imap_parse_adrlist (stream,txtptr,reply);
  1473.     (*env)->to = imap_parse_adrlist (stream,txtptr,reply);
  1474.     (*env)->cc = imap_parse_adrlist (stream,txtptr,reply);
  1475.     (*env)->bcc = imap_parse_adrlist (stream,txtptr,reply);
  1476.     (*env)->in_reply_to = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1477.     (*env)->message_id = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1478.     if (**txtptr != ')') {
  1479.       sprintf (LOCAL->tmp,"Junk at end of envelope: %.80s",*txtptr);
  1480.       mm_log (LOCAL->tmp,WARN);
  1481.     }
  1482.     else ++*txtptr;        /* skip past delimiter */
  1483.     break;
  1484.   case 'N':            /* if NIL */
  1485.   case 'n':
  1486.     ++*txtptr;            /* bump past "I" */
  1487.     ++*txtptr;            /* bump past "L" */
  1488.     break;
  1489.   default:
  1490.     sprintf (LOCAL->tmp,"Not an envelope: %.80s",*txtptr);
  1491.     mm_log (LOCAL->tmp,WARN);
  1492.     break;
  1493.   }
  1494. }
  1495.  
  1496. /* Mail Access Protocol parse address list
  1497.  * Accepts: MAIL stream
  1498.  *        current text pointer
  1499.  *        parsed reply
  1500.  * Returns: address list, NIL on failure
  1501.  *
  1502.  * Updates text pointer
  1503.  */
  1504.  
  1505. ADDRESS *imap_parse_adrlist (MAILSTREAM *stream,char **txtptr,
  1506.                  IMAPPARSEDREPLY *reply)
  1507. {
  1508.   ADDRESS *adr = NIL;
  1509.   char c = **txtptr;        /* sniff at first character */
  1510.                 /* ignore leading spaces */
  1511.   while (c == ' ') c = *++*txtptr;
  1512.   ++*txtptr;            /* skip past open paren */
  1513.   switch (c) {
  1514.   case '(':            /* if envelope S-expression */
  1515.     adr = imap_parse_address (stream,txtptr,reply);
  1516.     if (**txtptr != ')') {
  1517.       sprintf (LOCAL->tmp,"Junk at end of address list: %.80s",*txtptr);
  1518.       mm_log (LOCAL->tmp,WARN);
  1519.     }
  1520.     else ++*txtptr;        /* skip past delimiter */
  1521.     break;
  1522.   case 'N':            /* if NIL */
  1523.   case 'n':
  1524.     ++*txtptr;            /* bump past "I" */
  1525.     ++*txtptr;            /* bump past "L" */
  1526.     break;
  1527.   default:
  1528.     sprintf (LOCAL->tmp,"Not an address: %.80s",*txtptr);
  1529.     mm_log (LOCAL->tmp,WARN);
  1530.     break;
  1531.   }
  1532.   return adr;
  1533. }
  1534.  
  1535. /* Mail Access Protocol parse address
  1536.  * Accepts: MAIL stream
  1537.  *        current text pointer
  1538.  *        parsed reply
  1539.  * Returns: address, NIL on failure
  1540.  *
  1541.  * Updates text pointer
  1542.  */
  1543.  
  1544. ADDRESS *imap_parse_address (MAILSTREAM *stream,char **txtptr,
  1545.                  IMAPPARSEDREPLY *reply)
  1546. {
  1547.   ADDRESS *adr = NIL;
  1548.   ADDRESS *ret = NIL;
  1549.   ADDRESS *prev = NIL;
  1550.   char c = **txtptr;        /* sniff at first address character */
  1551.   switch (c) {
  1552.   case '(':            /* if envelope S-expression */
  1553.     while (c == '(') {        /* recursion dies on small stack machines */
  1554.       ++*txtptr;        /* skip past open paren */
  1555.       if (adr) prev = adr;    /* note previous if any */
  1556.       adr = mail_newaddr ();    /* instantiate address and parse its fields */
  1557.       adr->personal = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1558.       adr->adl = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1559.       adr->mailbox = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1560.       adr->host = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1561.       if (**txtptr != ')') {    /* handle trailing paren */
  1562.     sprintf (LOCAL->tmp,"Junk at end of address: %.80s",*txtptr);
  1563.     mm_log (LOCAL->tmp,WARN);
  1564.       }
  1565.       else ++*txtptr;        /* skip past close paren */
  1566.       c = **txtptr;        /* set up for while test */
  1567.                 /* ignore leading spaces in front of next */
  1568.       while (c == ' ') c = *++*txtptr;
  1569.       if (!ret) ret = adr;    /* if first time note first adr */
  1570.                 /* if previous link new block to it */
  1571.       if (prev) prev->next = adr;
  1572.     }
  1573.     break;
  1574.  
  1575.   case 'N':            /* if NIL */
  1576.   case 'n':
  1577.     *txtptr += 3;        /* bump past NIL */
  1578.     break;
  1579.   default:
  1580.     sprintf (LOCAL->tmp,"Not an address: %.80s",*txtptr);
  1581.     mm_log (LOCAL->tmp,WARN);
  1582.     break;
  1583.   }
  1584.   return ret;
  1585. }
  1586.  
  1587. /* Mail Access Protocol parse flags
  1588.  * Accepts: current message cache
  1589.  *        current text pointer
  1590.  *
  1591.  * Updates text pointer
  1592.  */
  1593.  
  1594. void imap_parse_flags (MAILSTREAM *stream,MESSAGECACHE *elt,char **txtptr)
  1595. {
  1596.   char *flag;
  1597.   char c;
  1598.   elt->user_flags = NIL;    /* zap old flag values */
  1599.   elt->seen = elt->deleted = elt->flagged = elt->answered = elt->recent = NIL;
  1600.   while (T) {            /* parse list of flags */
  1601.     flag = ++*txtptr;        /* point at a flag */
  1602.                 /* scan for end of flag */
  1603.     while (**txtptr != ' ' && **txtptr != ')') ++*txtptr;
  1604.     c = **txtptr;        /* save delimiter */
  1605.     **txtptr = '\0';        /* tie off flag */
  1606.     if (*flag != '\0') {    /* if flag is non-null */
  1607.       if (*flag == '\\') {    /* if starts with \ must be sys flag */
  1608.     if (!strcmp (ucase (flag),"\\SEEN")) elt->seen = T;
  1609.     else if (!strcmp (flag,"\\DELETED")) elt->deleted = T;
  1610.     else if (!strcmp (flag,"\\FLAGGED")) elt->flagged = T;
  1611.     else if (!strcmp (flag,"\\ANSWERED")) elt->answered = T;
  1612.     else if (!strcmp (flag,"\\RECENT")) elt->recent = T;
  1613.                 /* coddle TOPS-20 server */
  1614.     else if (strcmp (flag,"\\XXXX") && strcmp (flag,"\\YYYY") &&
  1615.          strncmp (flag,"\\UNDEFINEDFLAG",14)) {
  1616.       sprintf (LOCAL->tmp,"Unknown system flag: %.80s",flag);
  1617.       mm_log (LOCAL->tmp,WARN);
  1618.     }
  1619.       }
  1620.                 /* otherwise user flag */
  1621.       else imap_parse_user_flag (stream,elt,flag);
  1622.     }
  1623.     if (c == ')') break;    /* quit if end of list */
  1624.   }
  1625.   ++*txtptr;            /* bump past delimiter */
  1626. }
  1627.  
  1628. /* Mail Access Protocol parse user flag
  1629.  * Accepts: message cache element
  1630.  *        flag name
  1631.  */
  1632.  
  1633. void imap_parse_user_flag (MAILSTREAM *stream,MESSAGECACHE *elt,char *flag)
  1634. {
  1635.   long i;
  1636.                   /* sniff through all user flags */
  1637.   for (i = 0; i < NUSERFLAGS; ++i)
  1638.                   /* match this one? */
  1639.     if (!strcmp (flag,stream->user_flags[i])) {
  1640.       elt->user_flags |= 1 << i;/* yes, set the bit for that flag */
  1641.       return;            /* and quit */
  1642.     }
  1643.   sprintf (LOCAL->tmp,"Unknown user flag: %.80s",flag);
  1644.   mm_log (LOCAL->tmp,WARN);
  1645. }
  1646.  
  1647. /* Mail Access Protocol parse string
  1648.  * Accepts: MAIL stream
  1649.  *        current text pointer
  1650.  *        parsed reply
  1651.  *        flag that it may be kept outside of free storage cache
  1652.  * Returns: string
  1653.  *
  1654.  * Updates text pointer
  1655.  */
  1656.  
  1657. char *imap_parse_string (MAILSTREAM *stream,char **txtptr,
  1658.              IMAPPARSEDREPLY *reply,long special)
  1659. {
  1660.   char *st;
  1661.   char *string = NIL;
  1662.   unsigned long i;
  1663.   char c = **txtptr;        /* sniff at first character */
  1664.                 /* ignore leading spaces */
  1665.   while (c == ' ') c = *++*txtptr;
  1666.   st = ++*txtptr;        /* remember start of string */
  1667.   switch (c) {
  1668.   case '"':            /* if quoted string */
  1669.     i = 1;            /* initial byte count */
  1670.     while (**txtptr != '"') {    /* search for end of string */
  1671.       ++i;            /* bump count */
  1672.       ++*txtptr;        /* bump pointer */
  1673.     }
  1674.     **txtptr = '\0';        /* tie off string */
  1675.     string = (char *) fs_get (i);
  1676.     strncpy (string,st,i);    /* copy the string */
  1677.     ++*txtptr;            /* bump past delimiter */
  1678.     break;
  1679.   case 'N':            /* if NIL */
  1680.   case 'n':
  1681.     ++*txtptr;            /* bump past "I" */
  1682.     ++*txtptr;            /* bump past "L" */
  1683.     break;
  1684.  
  1685.   case '{':            /* if literal string */
  1686.                 /* get size of string */
  1687.     i = imap_parse_number (stream,txtptr);
  1688.     if (special && mailgets)    /* have special routine to slurp string? */
  1689.       string = (*mailgets) (tcp_getbuffer,LOCAL->tcpstream,i);
  1690.     else {            /* must slurp into free storage */
  1691.       string = (char *) fs_get (i + 1);
  1692.       *string = '\0';        /* init in case getbuffer fails */
  1693.                 /* get the literal */
  1694.       tcp_getbuffer (LOCAL->tcpstream,i,string);
  1695.     }
  1696.     fs_give ((void **) &reply->line);
  1697.                 /* get new reply text line */
  1698.     reply->line = tcp_getline (LOCAL->tcpstream);
  1699.     if (stream->debug) mm_dlog (reply->line);
  1700.     *txtptr = reply->line;    /* set text pointer to point at it */
  1701.     break;
  1702.   default:
  1703.     sprintf (LOCAL->tmp,"Not a string: %c%.80s",c,*txtptr);
  1704.     mm_log (LOCAL->tmp,WARN);
  1705.     break;
  1706.   }
  1707.   return string;
  1708. }
  1709.  
  1710.  
  1711. /* Mail Access Protocol parse number
  1712.  * Accepts: MAIL stream
  1713.  *        current text pointer
  1714.  * Returns: number parsed
  1715.  *
  1716.  * Updates text pointer
  1717.  */
  1718.  
  1719. unsigned long imap_parse_number (MAILSTREAM *stream,char **txtptr)
  1720. {                /* parse number */
  1721.   long i = strtol (*txtptr,txtptr,10);
  1722.   if (i < 0) {            /* number valid? */
  1723.     sprintf (LOCAL->tmp,"Bad number: %ld",i);
  1724.     mm_log (LOCAL->tmp,WARN);
  1725.     i = 0;            /* make sure valid */
  1726.   }
  1727.   return (unsigned long) i;
  1728. }
  1729.  
  1730. /* Mail Access Protocol parse body structure or contents
  1731.  * Accepts: MAIL stream
  1732.  *        pointer to body pointer
  1733.  *        pointer to segment
  1734.  *        current text pointer
  1735.  *        parsed reply
  1736.  *
  1737.  * Updates text pointer, stores body
  1738.  */
  1739.  
  1740. void imap_parse_body (MAILSTREAM *stream,long msgno,BODY **body,char *seg,
  1741.               char **txtptr,IMAPPARSEDREPLY *reply)
  1742. {
  1743.   char *s;
  1744.   unsigned long i;
  1745.   BODY *b;
  1746.   PART *part;
  1747.   switch (*seg++) {        /* dispatch based on type of data */
  1748.   case '\0':            /* body structure */
  1749.     mail_free_body (body);    /* flush any prior body */
  1750.                 /* instantiate and parse a new body */
  1751.     imap_parse_body_structure (stream,*body = mail_newbody (),txtptr,reply);
  1752.     break;
  1753.   case '[':            /* body section text */
  1754.     if ((!(s = strchr (seg,']'))) || s[1]) {
  1755.       sprintf (LOCAL->tmp,"Bad body index: %.80s",seg);
  1756.       mm_log (LOCAL->tmp,WARN);
  1757.       return;
  1758.     }
  1759.     *s = '\0';            /* tie off section specifier */
  1760.                 /* get the body section text */
  1761.     s = imap_parse_string (stream,txtptr,reply,msgno);
  1762.     if (!(b = *body)) {        /* must have structure first */
  1763.       mm_log ("Body contents received when body structure unknown",WARN);
  1764.       fs_give ((void **) &s);
  1765.       return;
  1766.     }
  1767.                 /* get first section number */
  1768.     if (!(seg && *seg && ((i = strtol (seg,&seg,10)) > 0))) {
  1769.       mm_log ("Bogus section number",WARN);
  1770.       fs_give ((void **) &s);
  1771.       return;
  1772.     }
  1773.  
  1774.     do {            /* multipart content? */
  1775.       if (b->type == TYPEMULTIPART) {
  1776.     part = b->contents.part;/* yes, find desired part */
  1777.     while (--i && (part = part->next));
  1778.     if (!part || (((b = &part->body)->type == TYPEMULTIPART) && !*s)) {
  1779.       mm_log ("Bad section number",WARN);
  1780.       fs_give ((void **) &s);
  1781.       return;
  1782.     }
  1783.       }
  1784.       else if (i != 1) {    /* otherwise must be section 1 */
  1785.     mm_log ("Invalid section number",WARN);
  1786.     fs_give ((void **) &s);
  1787.     return;
  1788.       }
  1789.                 /* need to go down further? */
  1790.       if (i = *seg) switch (b->type) {
  1791.       case TYPEMESSAGE:        /* embedded message, get body */
  1792.     b = b->contents.msg.body;
  1793.       case TYPEMULTIPART:    /* multipart, get next section */
  1794.     if ((*seg++ == '.') && (i = strtol (seg,&seg,10)) > 0) break;
  1795.       default:            /* bogus subpart */
  1796.     mm_log ("Invalid sub-section",WARN);
  1797.     fs_give ((void **) &s);
  1798.     return;
  1799.       }
  1800.     } while (i);
  1801.     if (b) switch (b->type) {    /* decide where the data goes based on type */
  1802.     case TYPEMULTIPART:        /* nothing to fetch with these */
  1803.       mm_log ("Textual body contents received for MULTIPART body part",WARN);
  1804.       fs_give ((void **) &s);
  1805.       return;
  1806.     case TYPEMESSAGE:        /* encapsulated message */
  1807.       fs_give ((void **) &b->contents.msg.text);
  1808.       b->contents.msg.text = s;
  1809.       break;
  1810.     case TYPETEXT:        /* textual data */
  1811.       fs_give ((void **) &b->contents.text);
  1812.       b->contents.text = (unsigned char *) s;
  1813.       break;
  1814.     default:            /* otherwise assume it is binary */
  1815.       fs_give ((void **) &b->contents.binary);
  1816.       b->contents.binary = (void *) s;
  1817.       break;
  1818.     }
  1819.     break;
  1820.   default:            /* bogon */
  1821.     sprintf (LOCAL->tmp,"Bad body fetch: %.80s",seg);
  1822.     mm_log (LOCAL->tmp,WARN);
  1823.     return;
  1824.   }
  1825. }
  1826.  
  1827. /* Mail Access Protocol parse body structure
  1828.  * Accepts: MAIL stream
  1829.  *        current text pointer
  1830.  *        parsed reply
  1831.  *        body structure to write into
  1832.  *
  1833.  * Updates text pointer
  1834.  */
  1835.  
  1836. void imap_parse_body_structure (MAILSTREAM *stream,BODY *body,char **txtptr,
  1837.                 IMAPPARSEDREPLY *reply)
  1838. {
  1839.   char *s;
  1840.   PART *part = NIL;
  1841.   PARAMETER *param = NIL;
  1842.   char c = *((*txtptr)++);    /* grab first character */
  1843.                 /* ignore leading spaces */
  1844.   while (c == ' ') c = *((*txtptr)++);
  1845.   switch (c) {            /* dispatch on first character */
  1846.   case '(':            /* body structure list */
  1847.     if (**txtptr == '(') {    /* multipart body? */
  1848.       body->type= TYPEMULTIPART;/* yes, set its type */
  1849.       do {            /* instantiate new body part */
  1850.     if (part) part = part->next = mail_newbody_part ();
  1851.     else body->contents.part = part = mail_newbody_part ();
  1852.                 /* parse it */
  1853.     imap_parse_body_structure (stream,&part->body,txtptr,reply);
  1854.       } while (**txtptr == '(');/* for each body part */
  1855.       if (!(body->subtype = imap_parse_string (stream,txtptr,reply,(long)NIL)))
  1856.     mm_log ("Missing multipart subtype",WARN);
  1857.       if (**txtptr != ')') {    /* validate ending */
  1858.     sprintf (LOCAL->tmp,"Junk at end of multipart body: %.80s",*txtptr);
  1859.     mm_log (LOCAL->tmp,WARN);
  1860.       }
  1861.       else ++*txtptr;        /* skip past delimiter */
  1862.     }
  1863.  
  1864.     else {            /* not multipart, parse type name */
  1865.       if (**txtptr == ')') {    /* empty body? */
  1866.     ++*txtptr;        /* bump past it */
  1867.     break;            /* and punt */
  1868.       }
  1869.       body->type = TYPEOTHER;    /* assume unknown type */
  1870.       body->encoding = ENCOTHER;/* and unknown encoding */
  1871.                 /* parse type */
  1872.       if (s = imap_parse_string (stream,txtptr,reply,(long) NIL)) {
  1873.     ucase (s);        /* make parse easier */
  1874.     switch (*s) {        /* dispatch based on type */
  1875.     case 'A':        /* APPLICATION or AUDIO */
  1876.       if (!strcmp (s+1,"PPLICATION")) body->type = TYPEAPPLICATION;
  1877.       else if (!strcmp (s+1,"UDIO")) body->type = TYPEAUDIO;
  1878.       break;
  1879.     case 'I':        /* IMAGE */
  1880.       if (!strcmp (s+1,"MAGE")) body->type = TYPEIMAGE;
  1881.       break;
  1882.     case 'M':        /* MESSAGE */
  1883.       if (!strcmp (s+1,"ESSAGE")) body->type = TYPEMESSAGE;
  1884.       break;
  1885.     case 'T':        /* TEXT */
  1886.       if (!strcmp (s+1,"EXT")) body->type = TYPETEXT;
  1887.       break;
  1888.     case 'V':        /* VIDEO */
  1889.       if (!strcmp (s+1,"IDEO")) body->type = TYPEVIDEO;
  1890.       break;
  1891.     default:
  1892.       break;
  1893.     }
  1894.     fs_give ((void **) &s);    /* flush the string */
  1895.       }
  1896.                 /* parse subtype */
  1897.       body->subtype = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1898.  
  1899.       body->parameter = NIL;    /* init parameter list */
  1900.  
  1901.       c = *(*txtptr)++;        /* sniff at first character */
  1902.                 /* ignore leading spaces */
  1903.       while (c == ' ') c = *(*txtptr)++;
  1904.                 /* parse parameter list */
  1905.       if (c == '(') while (c != ')') {
  1906.     if (body->parameter)    /* append new parameter to tail */
  1907.       param = param->next = mail_newbody_parameter ();
  1908.     else body->parameter = param = mail_newbody_parameter ();
  1909.     if (!(param->attribute = imap_parse_string (stream,txtptr,reply,
  1910.                             (long) NIL))){
  1911.       mm_log ("Missing parameter attribute",WARN);
  1912.       break;
  1913.     }
  1914.     if (!(param->value = imap_parse_string (stream,txtptr,reply,
  1915.                         (long) NIL))) {
  1916.       sprintf (LOCAL->tmp,"Missing value for parameter %.80s",
  1917.            param->attribute);
  1918.       mm_log (LOCAL->tmp,WARN);
  1919.       break;
  1920.     }
  1921.     switch (c = **txtptr) {    /* see what comes after */
  1922.     case ' ':        /* flush whitespace */
  1923.       while ((c = *++*txtptr) == ' ');
  1924.       break;
  1925.     case ')':        /* end of attribute/value pairs */
  1926.       ++*txtptr;        /* skip past closing paren */
  1927.       break;
  1928.     default:
  1929.       sprintf (LOCAL->tmp,"Junk at end of parameter: %.80s",s);
  1930.       mm_log (LOCAL->tmp,WARN);
  1931.       break;
  1932.     }
  1933.       }
  1934.       else {            /* empty parameter, must be NIL */
  1935.     if (((c == 'N') || (c == 'n')) &&
  1936.         ((*(s = *txtptr) == 'I') || (*s == 'i')) &&
  1937.         ((s[1] == 'L') || (s[1] == 'l')) && (s[2] == ' ')) *txtptr += 2;
  1938.     else {
  1939.       sprintf (LOCAL->tmp,"Bogus body parameter: %c%.80s",c,s);
  1940.       mm_log (LOCAL->tmp,WARN);
  1941.       break;
  1942.     }
  1943.       }
  1944.  
  1945.       body->id = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1946.       body->description = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1947.       if (s = imap_parse_string (stream,txtptr,reply,(long) NIL)) {
  1948.     ucase (s);        /* make parse easier */
  1949.     switch (*s) {        /* dispatch based on encoding */
  1950.     case '7':        /* 7BIT */
  1951.       if (!strcmp (s+1,"BIT")) body->encoding = ENC7BIT;
  1952.       break;
  1953.     case '8':        /* 8BIT */
  1954.       if (!strcmp (s+1,"BIT")) body->encoding = ENC8BIT;
  1955.       break;
  1956.     case 'B':        /* BASE64 or BINARY */
  1957.       if (!strcmp (s+1,"ASE64")) body->encoding = ENCBASE64;
  1958.       else if (!strcmp (s,"INARY")) body->encoding = ENCBINARY;
  1959.       break;
  1960.     case 'Q':        /* QUOTED-PRINTABLE */
  1961.       if (!strcmp (s+1,"UOTED-PRINTABLE"))
  1962.         body->encoding = ENCQUOTEDPRINTABLE;
  1963.       break;
  1964.     default:
  1965.       break;
  1966.     }
  1967.     fs_give ((void **) &s);    /* flush the string */
  1968.       }
  1969.                 /* parse size of contents in bytes */
  1970.       body->size.bytes = imap_parse_number (stream,txtptr);
  1971.       switch (body->type) {    /* possible extra stuff */
  1972.       case TYPEMESSAGE:        /* message envelope and body */
  1973.     if (strcmp (body->subtype,"RFC822")) break;
  1974.     imap_parse_envelope (stream,&body->contents.msg.env,txtptr,reply);
  1975.     body->contents.msg.body = mail_newbody ();
  1976.     imap_parse_body_structure(stream,body->contents.msg.body,txtptr,reply);
  1977.                 /* drop into text case */
  1978.       case TYPETEXT:        /* size in lines */
  1979.     body->size.lines = imap_parse_number (stream,txtptr);
  1980.     break;
  1981.       default:            /* otherwise nothing special */
  1982.     break;
  1983.       }
  1984.       if (**txtptr != ')') {    /* validate ending */
  1985.     sprintf (LOCAL->tmp,"Junk at end of body part: %.80s",*txtptr);
  1986.     mm_log (LOCAL->tmp,WARN);
  1987.       }
  1988.       else ++*txtptr;        /* skip past delimiter */
  1989.     }
  1990.     break;
  1991.  
  1992.   case 'N':            /* if NIL */
  1993.   case 'n':
  1994.     ++*txtptr;            /* bump past "I" */
  1995.     ++*txtptr;            /* bump past "L" */
  1996.     break;
  1997.   default:            /* otherwise quite bogus */
  1998.     sprintf (LOCAL->tmp,"Bogus body structure: %.80s",*txtptr);
  1999.     mm_log (LOCAL->tmp,WARN);
  2000.     break;
  2001.   }
  2002. }
  2003.